﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Xml;

public class CmdLineAnalyzer
{
	/////////////////////////////////
	//Locations to find various resources
	public static string ROOT_STRING = BaseDirString();

	private static string BaseDirString()
	{
		String cp = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
		int cmdloc = cp.LastIndexOf(@"jackalope");
		if(cmdloc != -1)
		{
			cmdloc = cp.IndexOf(@"\", cmdloc);
			cmdloc = cp.IndexOf(@"\", cmdloc + 1);
		}

		if(cmdloc == -1)
		{
			Console.WriteLine("Cannot move outside of jackalope directory!!!");
			System.Environment.Exit(1);
		}

		string rp = cp.Substring(0, cmdloc + 1);
		return rp;
	}

	public static string TEST_DIR = ROOT_STRING + @"Data\Tests";
	public static string RESULT_DIR_FULLCTX = ROOT_STRING + @"Data\TestResults\";
	public static string DUMP_DIR = ROOT_STRING + @"Data\";
	public static string SERIALIZE_DIR = ROOT_STRING + @"Data\ResultsDump";

	/////////////////////////////////
	//Files for testing and evaluation

	//Set to update all the unit test results
	private static bool REGEN_TEST_RESULTS = false;
	
	//Simple tests
	public static string FilterMarkSumExe = TEST_DIR + @"\Simple\FilterMarkSumExample\FilterMarkSumExample\bin\Debug\FilterMarkSumExample.exe";
	public static string TListExe = TEST_DIR + @"\Simple\TList\TList\bin\Debug\TList.exe";
	public static string RListExe = TEST_DIR + @"\Simple\RList\RList\bin\Debug\RList.exe";
	public static string TreeExe = TEST_DIR + @"\Simple\Tree\Tree\bin\Debug\Tree.exe";
	public static string MRaytracerExe = TEST_DIR + @"\Simple\MRaytracer\MRaytracer\bin\Debug\MRaytracer.exe";
	public static string LibQuantumExe = TEST_DIR + @"\Simple\LibQuantum\LibQuantum\bin\Debug\LibQuantum.exe";

	public static string StructAndFuncExe = TEST_DIR + @"\Simple\DelegateTests\DelegateTests\bin\Debug\DelegateTests.exe";
	public static string SetAndDictExe = TEST_DIR + @"\Simple\SetAndDict\SetAndDict\bin\Debug\SetAndDict.exe";

	//csolden tests
	public static string BHExe = TEST_DIR + @"\Olden\BH\BH\bin\Debug\BH.exe";
	public static string BiSortExe = TEST_DIR + @"\Olden\BiSort\BiSort\bin\Debug\BiSort.exe";
	public static string Em3dExe = TEST_DIR + @"\Olden\Em3d\Em3d\bin\Debug\Em3d.exe";
	public static string HealthExe = TEST_DIR + @"\Olden\Health\Health\bin\Debug\Health.exe";
	public static string PerimeterExe = TEST_DIR + @"\Olden\Perimeter\Perimeter\bin\Debug\Perimeter.exe";
	public static string PowerExe = TEST_DIR + @"\Olden\Power\Power\bin\Debug\Power.exe";
	public static string TSPExe = TEST_DIR + @"\Olden\TSP\TSP\bin\Debug\TSP.exe";

	//csspec tests
	public static string DBExe = TEST_DIR + @"\Spec\DB\DB\bin\Debug\DB.exe";
	public static string SpecRaytraceExe = TEST_DIR + @"\Spec\SpecRaytracer\SpecRaytracer\bin\Debug\SpecRaytracer.exe";

	//large tests
	public static string LUIndexExe = TEST_DIR + @"\Large\luindex\bin\NLucene.exe";
	public static string LUSearchExe = TEST_DIR + @"\Large\lusearch\bin\NLucene2.exe";
	public static string RunAbsExe = TEST_DIR + @"\Large\HeapAbs\HeapAbs\bin\Debug\HeapAbs.exe";

	/////////////////////////////////
	//Quick flags to set to control default behaviors, easier than build flags or cmdline params for timing tests, regenerating desired output info, etc.
	private static bool RunInSingleMode = true;
	private static string SingleFileToAnalyze = MRaytracerExe;

	public static void Main(string[] args)
	{
		if(RunInSingleMode)
		{
			string exeasm = (args.Length != 0 ? args[0] : SingleFileToAnalyze);
			AnalyzeAndReportSingleExecFile(exeasm, false);
		}
		else
			RunAllFiles();
	}

	private static void RunAllFiles()
	{
		RunAllFilesDoSingle(FilterMarkSumExe);
		RunAllFilesDoSingle(TListExe);
		RunAllFilesDoSingle(RListExe);
		RunAllFilesDoSingle(TreeExe);
		RunAllFilesDoSingle(MRaytracerExe);
		RunAllFilesDoSingle(LibQuantumExe);

		RunAllFilesDoSingle(BHExe);
		RunAllFilesDoSingle(BiSortExe);
		RunAllFilesDoSingle(Em3dExe);
		RunAllFilesDoSingle(HealthExe);
		RunAllFilesDoSingle(PerimeterExe);
		RunAllFilesDoSingle(PowerExe);
		RunAllFilesDoSingle(TSPExe);
		
		RunAllFilesDoSingle(DBExe);
		RunAllFilesDoSingle(SpecRaytraceExe);

		RunAllFilesDoSingle(StructAndFuncExe);
		RunAllFilesDoSingle(SetAndDictExe);

		RunAllFilesDoSingle(LUIndexExe);

		Console.WriteLine("Skipping LUSearch because it serializes a lot of data!!");
		//RunAllFilesDoSingle(LUSearchExe);

		//RunAllFilesDoSingle(RunAbsExe);
	}

	private static void RunAllFilesDoSingle(string fname)
	{
		string baseExec = System.IO.Path.GetFileName(fname);

		string hs = "*********** " + baseExec + " ***********";
		string hhs = "";
		for(int kk = 0; kk < hs.Length; ++kk)
			hhs += "*";
		Console.WriteLine("\n\n" + hhs + "\n" + hs + "\n");

		AnalyzeAndReportSingleExecFile(fname, false);
	}

	public static string AnalyzeAndReportSingleExecFile(string exeasm, bool intesting)
	{
		System.Diagnostics.Stopwatch timerload = new System.Diagnostics.Stopwatch();
		timerload.Start();

		SimpleIR.IRProgram p = CCITranslator.CCIExtractor.Extract(exeasm, ROOT_STRING);
		p.LoadTypeInfo();
		p.PreProcLinkAndSimplify();

		p.BuildT1T2();

		timerload.Stop();

		if(REGEN_TEST_RESULTS)
			DUMP_DIR = System.IO.Path.Combine(RESULT_DIR_FULLCTX + System.IO.Path.ChangeExtension(System.IO.Path.GetFileName(exeasm), null) + @"\");

		if(System.IO.Directory.Exists(System.IO.Path.GetDirectoryName(DUMP_DIR)))
		{
			String fstr = p.ToString();
			Console.WriteLine("Writing Dump.");
			System.IO.File.WriteAllText(System.IO.Path.Combine(DUMP_DIR, "program.dbg"), fstr);
		}
		p.ReportOnProgramMetrics();

		RgnAnalyzer.GlobalAnalysisState.LoadProg(p, DUMP_DIR, SERIALIZE_DIR);

		//do the analysis
		System.Diagnostics.Stopwatch timeranalyze = new System.Diagnostics.Stopwatch();
		timeranalyze.Start();
		RgnAnalyzer.GlobalAnalysisState.AnalyzeMain(intesting);
		timeranalyze.Stop();

		//time post processing
		System.Diagnostics.Stopwatch timerpost = new System.Diagnostics.Stopwatch();
		timerpost.Start();

		if(System.IO.Directory.Exists(System.IO.Path.GetDirectoryName(DUMP_DIR)))
		{
			StringBuilder xmlsb = new StringBuilder();
			System.Xml.XmlWriterSettings settings = new System.Xml.XmlWriterSettings { Indent = true, OmitXmlDeclaration = true, CloseOutput = true, Encoding = Encoding.UTF8 };

			using(var xw = XmlWriter.Create(xmlsb, settings))
			{
				p.CallGraph.SerializeToDGML(xw, p);
			}

			Console.WriteLine("Writing Resolved Call Graph.");
			System.IO.File.WriteAllText(System.IO.Path.Combine(DUMP_DIR, "callg.dgml"), xmlsb.ToString());
		}

		Console.WriteLine("Computing Completed Information (this may take a bit of time).");
		PInfoModel.CompleteAnalysisInfoForProgram fpinfo = RgnAnalyzer.GlobalAnalysisState.ComputeFullAnalysisInfo(true);

		Console.WriteLine("Computing Basic Results.");
		PInfoModel.Analyses.AnalyisSupport.ComputeAndWriteStdAnalysisResults(fpinfo, System.IO.Path.Combine(DUMP_DIR, "resdata.output"));

		////
		//Mutation and nullity
		PIPostAnalysis.ValueTypes.MutationAndNullityCheckInfo mnci = PIPostAnalysis.ValueTypes.MutationAndNullityChecksExtractor.ExtractMutationAndNullityForProg(fpinfo);
		System.IO.File.WriteAllText(System.IO.Path.Combine(DUMP_DIR, "mutationnullity.output"), mnci.EmitString());

		////
		//Structure Specifications
		PIPostAnalysis.StructureSpecs.StructureSpec specs = PIPostAnalysis.StructureSpecs.StructSpecExtractor.ProcessProgram(fpinfo);
		PInfoModel.Analyses.TypeAndMethodConstnessInfo constinfo = PInfoModel.Analyses.TypeAndMethodConstnessExtractor.ExtractGeneralInfo(fpinfo);
		System.IO.File.WriteAllText(System.IO.Path.Combine(DUMP_DIR, "specs.output"), specs.EmitSpecsString(constinfo.ConstClasses, new HashSet<SimpleIR.IRType>()));

		////
		//Clean up
		PInfoModel.CompleteAnalysisInfoMethodContext.CleanUpProgPointInfoTables();

		timerpost.Stop();

		return ReportOnProgram(timerload, timeranalyze, timerpost, intesting, fpinfo);
	}

	/// <summary>
	/// Output some general information about the run and if not <c>intesting</c> allow for interactive inspection of the results.
	/// </summary>
	public static String ReportOnProgram(System.Diagnostics.Stopwatch timerload, System.Diagnostics.Stopwatch timeranalyze, System.Diagnostics.Stopwatch timerpost, bool intesting, PInfoModel.CompleteAnalysisInfoForProgram pip)
	{
		StringBuilder sb = new StringBuilder();

		double averageCallStateSize;
		double averageProcCount;
		int maxCallStateSize;
		int maxproccount;
		int maxnodes;
		int maxedges;

		RgnAnalyzer.GlobalAnalysisState.TAnalysisState.ComputeAverageMemoContextInfo(out averageCallStateSize, out averageProcCount, out maxCallStateSize, out maxproccount, out maxnodes, out maxedges);

		sb.Append("\n\n------------\nAnalysis Statistics:\n");

		sb.Append(GenRuntimeInfoReport(averageCallStateSize, averageProcCount, maxCallStateSize, maxproccount, maxnodes, maxedges, intesting));

		if(intesting)
		{
			System.Diagnostics.Debug.WriteLine("Load Time:         " + timerload.Elapsed.TotalSeconds);
			System.Diagnostics.Debug.WriteLine("Analyze Time:      " + timeranalyze.Elapsed.TotalSeconds);
			System.Diagnostics.Debug.WriteLine("Post Process Time: " + timerpost.Elapsed.TotalSeconds);
			return sb.ToString();
		}
		else
		{
			Console.WriteLine(sb.ToString());
			Console.WriteLine("Load Time:         " + timerload.Elapsed.TotalSeconds);
			Console.WriteLine("Analyze Time:      " + timeranalyze.Elapsed.TotalSeconds);
			Console.WriteLine("Post Process Time: " + timerpost.Elapsed.TotalSeconds);

			String cmd;

			bool isStat;
			String mname;
			String cname;
			int sloc;

			while(true)
			{
				Console.Write("query> ");
				cmd = Console.ReadLine();

				if(cmd.StartsWith("m ") || cmd.StartsWith("model "))
				{
					bool ok = ExtractMethodInfoFromUserCmd(cmd, out isStat, out cname, out mname, out sloc);

					if(!ok)
						Console.WriteLine("Could not parse: " + cmd);
					else
					{
						SimpleIR.IRBodyDef b = RgnAnalyzer.GlobalAnalysisState.TProg.MethodBodyTable.GetBodyWithNameInClassOrNull(isStat, cname, mname, null);
						if(b == null)
							Console.WriteLine("Could not resolve method!");
						else if(b.IsBuiltin)
							Console.WriteLine("Method is BuiltIn, has specialized semantics.");
						else
						{
							int dummy;
							List<Tuple<RgnAnalyzer.Domain.AProgramMemory, RgnAnalyzer.Domain.AProgramMemory, RgnAnalyzer.Domain.AProgramMemory>> dispset = RgnAnalyzer.GlobalAnalysisState.TAnalysisState.GetDisplayAndAnalysisPairsForBody(b, out dummy);

							if(dispset.Count == 0)
								Console.WriteLine("Method is never executed.");
							else
							{
								Console.WriteLine("Method has " + dispset.Count + " Context(s).");

								if(System.IO.Directory.Exists(System.IO.Path.GetDirectoryName(RgnAnalyzer.GlobalAnalysisState.GlobalDataDir)))
								{
									for(int i = 0; i < dispset.Count; ++i)
									{
										PInfoModel.PIModel pimin = dispset[i].Item1.MakePIModelForThis();
										String dgmlsin = pimin.DGMLDumpPModel(-1);
										System.IO.File.WriteAllText(System.IO.Path.Combine(RgnAnalyzer.GlobalAnalysisState.GlobalDataDir, String.Format("state_{0}_in.dgml", i)), dgmlsin);

										if(dispset[i].Item2 != null && (!dispset[i].Item2.IsInfeasable))
										{
											PInfoModel.PIModel pimout = dispset[i].Item2.MakePIModelForThis();
											String dgmlsout = pimout.DGMLDumpPModel(-1);
											System.IO.File.WriteAllText(System.IO.Path.Combine(RgnAnalyzer.GlobalAnalysisState.GlobalDataDir, String.Format("state_{0}_outproc.dgml", i)), dgmlsout);

											PInfoModel.PIModel pimoutreturn = dispset[i].Item3.MakePIModelForThis();
											String dgmlsoutreturn = pimoutreturn.DGMLDumpPModel(-1);
											System.IO.File.WriteAllText(System.IO.Path.Combine(RgnAnalyzer.GlobalAnalysisState.GlobalDataDir, String.Format("state_{0}_outreturn.dgml", i)), dgmlsoutreturn);
										}
									}
								}
							}
						}
					}
				}
				else if(cmd.StartsWith("mc ") || cmd.StartsWith("mapcall "))
				{
					if(cmd.Trim().EndsWith(" -full"))
					{
						PInfoModel.Analyses.IDMapAtCallInfoForFullProgram cmfp = PInfoModel.Analyses.IDMapAtCallInfoForFullProgram.MakeIDMappingForProgram(RgnAnalyzer.GlobalAnalysisState.GlobalDataDir, pip);
						cmfp.XMLDump();
					}
					else
					{
						bool ok = ExtractMethodInfoFromUserCmd(cmd, out isStat, out cname, out mname, out sloc);

						if(!ok)
							Console.WriteLine("Could not parse: " + cmd);
						else
						{
							SimpleIR.IRBodyDef b = RgnAnalyzer.GlobalAnalysisState.TProg.MethodBodyTable.GetBodyWithNameInClassOrNull(isStat, cname, mname, null);
							if(b == null)
								Console.WriteLine("Could not resolve method!");
							else if(b.IsBuiltin)
								Console.WriteLine("Method is BuiltIn, has specialized semantics.");
							else
							{
								Console.Write("Input line number to check> ");
								string rrs = Console.ReadLine().Trim();
								int lineval;
								bool okval = Int32.TryParse(rrs, out lineval);
								Console.WriteLine();

								if(!okval)
									Console.WriteLine("Could not parse line!");
								else
								{
									PInfoModel.Analyses.IDMapAtCallInfo idmc = PInfoModel.Analyses.IDMapAtCallInfo.DoInfoExtract(pip, b, lineval, RgnAnalyzer.GlobalAnalysisState.GlobalDataDir);
									idmc.PresentInfo();
								}
							}
						}
					}
				}
				else if(cmd.StartsWith("cg") || cmd.StartsWith("callgraph"))
				{
					System.Xml.XmlWriterSettings settings = new System.Xml.XmlWriterSettings { Indent = true, OmitXmlDeclaration = true, CloseOutput = true, Encoding = Encoding.UTF8 };

					try
					{
						String ffn = System.IO.Path.GetFullPath(RgnAnalyzer.GlobalAnalysisState.GlobalDataDir + "callgraph.dgml");
						if(System.IO.File.Exists(ffn))
							System.IO.File.Delete(ffn);

						using(System.Xml.XmlWriter cgwriter = System.Xml.XmlTextWriter.Create(ffn, settings))
						{
							RgnAnalyzer.GlobalAnalysisState.TProg.CallGraph.SerializeToDGML(cgwriter, RgnAnalyzer.GlobalAnalysisState.TProg);
						}
					}
					catch(System.IO.IOException)
					{
						Console.WriteLine("Unable to write call graph!!");
					}

				}
				else if(cmd.StartsWith("q") || cmd.StartsWith("quit"))
				{
					return "quitting";
				}
				else
				{
					Console.WriteLine("Unknown Command:");
					Console.WriteLine("model [m]: Models for method (class@mname or class::mname).");
					Console.WriteLine("mapcall [mc]: Call mapping info dump (class@mname or class::mname).");
					Console.WriteLine("callgraph [cg]: Call Graph.");
					Console.WriteLine("quit [q]: Exit analyzer.");
				}
			}
		}
	}

	public static String GenRuntimeInfoReport(double averageCallStateSize, double averageProcCount, int maxCallStateSize, int maxproccount, int maxnodes, int maxedges, bool intesting)
	{
		StringBuilder sb = new StringBuilder("------\nRuntime Report:\n\n");

		sb.Append("Max Loop Fixpoint Iters = " + RgnAnalyzer.GlobalAnalysisState.MaxLoopFixIters + "\n");
		sb.Append("Max Visits to Method = " + maxproccount + "\n");
		sb.Append("Max Memo Contexts = " + maxCallStateSize + "\n");
		sb.Append("\n");
		sb.Append("Avg Memo Contexts = " + averageCallStateSize + "\n");

		if(intesting)
			sb.Append("Avg Iters Per Context (ceil) = " + Math.Ceiling(averageProcCount) + "\n");
		else
			sb.Append("Avg Iters Per Context = " + averageProcCount + "\n");

		sb.Append("\n");
		sb.Append("Max Nodes = " + maxnodes + "\n");
		sb.Append("Max Edges = " + maxedges + "\n");
		sb.Append("------\n");

		return sb.ToString();
	}

	/// <summary>
	/// Extract the method name info from a user command, return false if bad input.
	/// </summary>
	private static bool ExtractMethodInfoFromUserCmd(String cmd, out bool isstat, out String cname, out String mname, out int sloc)
	{
		int sl = cmd.IndexOf(' ');
		int bl = cmd.IndexOf("::");
		int ml = cmd.IndexOf("@");

		isstat = false;
		cname = null;
		mname = null;
		sloc = -1;

		if(sl == -1 || (bl == -1 && ml == -1))
			return false;

		if(bl != -1)
		{
			int fl = cmd.IndexOf(' ', bl + 2);
			if(fl == -1)
				fl = cmd.Length;

			isstat = true;
			cname = cmd.Substring(sl, bl - sl).Trim();
			mname = cmd.Substring(bl + 2, fl - (bl + 2)).Trim();
		}
		else
		{
			int fl = cmd.IndexOf(' ', ml + 1);
			if(fl == -1)
				fl = cmd.Length;

			isstat = false;
			cname = cmd.Substring(sl, ml - sl).Trim();
			mname = cmd.Substring(ml + 1, fl - (ml + 1)).Trim();
		}

		int llp = cmd.IndexOf(" -l ");
		if(llp != -1)
		{
			String locs = cmd.Substring(llp + 4).Trim();
			bool ok = Int32.TryParse(locs, out sloc);
			if(!ok)
				return false;
		}

		return true;
	}
}

